package com.lifang.framework.aop.framework;

import cn.hutool.core.util.ClassUtil;
import com.lifang.framework.aop.AopProxy;
import com.lifang.framework.aop.framework.support.AdvisedSupport;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;
import java.util.List;

public class Cglib2AopProxy implements AopProxy {

    private final AdvisedSupport advised;

    public Cglib2AopProxy(AdvisedSupport advised) {
        this.advised = advised;
    }

    @Override
    public Object getProxy() {
        Class<?> rootClass = this.advised.getTargetSource().getTargetClass();
        Enhancer enhancer = new Enhancer();
        enhancer.setClassLoader(ClassUtil.getClassLoader());
        enhancer.setSuperclass(rootClass.getSuperclass());
        enhancer.setInterfaces(rootClass.getInterfaces());
        enhancer.setCallback(new DynamicAdvisedInterceptor(advised));
        return enhancer.create();
    }

    /**
     * cglib 动态代理调用方法实现接口
     * 所有实现了动态代理的基类，都不会直接调用原方法，而是由intercept方法决定是否调用
     */
    private static class DynamicAdvisedInterceptor implements MethodInterceptor {

        private final AdvisedSupport advised;

        public DynamicAdvisedInterceptor(AdvisedSupport advised) {
            this.advised = advised;
        }

        @Override
        public Object intercept(Object obj, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            Object target = advised.getTargetSource().getTarget();
            if (advised.hasMethodInterceptor()) {
                List<?> chain = advised.getInterceptorAndDynamicMethodMatchers();
                return new CglibMethodInvocation(target, method, objects, methodProxy, chain).proceed();
            } else {
                return method.invoke(target, objects);
            }
        }
    }

    private static class CglibMethodInvocation extends ReflectiveMethodInvocation {

        private final MethodProxy methodProxy;

        public CglibMethodInvocation(Object target, Method method, Object[] arguments, MethodProxy methodProxy, List<?> methodInterceptorList) {
            super(target, method, target.getClass(), arguments, methodInterceptorList);
            this.methodProxy = methodProxy;
        }

        @Override
        protected Object invokeJoinPoint() throws Throwable {
            if (this.methodProxy != null) {
                return this.methodProxy.invoke(this.target, this.arguments);
            }
            return super.invokeJoinPoint();
        }
    }

}
